import {copyDeep as arrCopyDeep} from "@/arr/utils/arr-copy-util.ts"

/**
 * 拷贝原始对象属性的属性值到目标对象目标属性的处理函数
 *
 * @param {Record<string, any>} origin 原始对象
 * @param {string} originPropName 原始对象属性名
 * @param originPropVal 原始对象属性值
 * @param {string} targetPropName 目标对象目标属性名
 * @param {Record<string, (origin: Record<string, any>, originPropName: string, targetPropName: string) => any>} targetPropValMap
 * 指定原始对象中属性的属性值拷贝到目标对象的目标属性时，目标对象目标属性与其值之间的映射，或者说是，原始对象属性的属性值拷贝到目标对象目标属性时的
 * 预处理操作
 * @returns {any} 处理后的属性值
 */
const copyPropValHandler = (
  origin: Record<string, any>,
  originPropName: string,
  originPropVal: any,
  targetPropName: string,
  targetPropValMap: Record<string, (origin: Record<string, any>, originPropName: string, targetPropName: string) => any>
) => {
  // 如果当前要拷贝到的目标对象目标属性的属性名指定了属性值的处理函数
  if (Reflect.has(targetPropValMap, targetPropName)) {
    // 获取当前要拷贝到的目标对象目标属性的属性值处理函数
    const handler = targetPropValMap[targetPropName]
    // 调用属性值处理函数，并返回处理后的属性值
    return handler(origin, originPropName, targetPropName)
  } else {
    return originPropVal
  }
}

/**
 * 深拷贝对象
 *
 * @param {Record<string, any>} origin 原始对象
 * @param {string[]} props 指定原始对象中需要拷贝的属性名数组，如果不指定则拷贝所有属性，如果在 props 中指定了要拷贝的属性，即 props 的
 * 长度大于 0，则不在 props 中的属性将不会被拷贝
 * @param {string[]} excludeProps 指定原始对象中需要排除拷贝的属性名数组，如果不指定则不排除任何属性，如果相同的属性名同时出现在 props
 * 和 excludeProps 中，excludeProps 优先级更高
 * @param {Record<string, string | string[]>} propsMap 指定原始对象中的属性与目标对象属性的映射关系，即原始对象中指定属性的属性值要拷贝
 * 到目标对象中的哪些属性上，如果要从原始对象拷贝到目标对象上的属性没有指定映射关系，则使用原始对象中的属性名作为目标对象上的属性名
 * @param {Record<string, (origin: Record<string, any>, originPropName: string, targetPropName: string) => any>} targetPropValMap
 * 指定原始对象中属性的属性值拷贝到目标对象的目标属性时，目标对象目标属性与其值之间的映射，或者说是，原始对象属性的属性值拷贝到目标对象目标属性时的
 * 预处理操作，因此需要一个函数，该函数接收目前正在拷贝的原始对象、目前正在拷贝的原始对象属性的属性名和要拷贝到目标对象目标属性的属性名作为参数
 * @param {((item: any) => boolean)} excludeArrItem 对于原始对象中属性值为数组的属性，数组中元素的排除条件，该函数接收当前遍历到的数组元
 * 素作为参数，返回 true 则表示排除该元素的拷贝，返回 false 则不排除该元素的拷贝，只有当当前遍历到的数组元素不为 undefined、null、function
 * 时才会调用该函数进行判断，默认实现为 `() => false`
 * @param {((a: any, b: any) => number) | null} sort 对于原始对象中属性值为数组的属性，该函数参数用于指定数组中元素的排序，该函数接收两
 * 个数组元素作为参数，该函数需要返回一个数字类型的数据，如果返回的为负数，a 排在 b 前面；如果返回 0，a 和 b 的相对位置不变；如果返回正数，a 排
 * 在 b 后面。当 sort 为 null 时，表示不对数组进行排序，默认实现为 `null`
 * @param {boolean} isCopyEmptyArr 对于原始对象中属性值为空数组的属性是否进行拷贝，默认值为 true
 * @param {boolean} isRemoveObjUndefined 对于原始对象中值为 undefined 的属性是否进行移除，不对其进行拷贝，默认值为 false
 * @param {boolean} isRemoveArrUndefined 对于原始对象中值为数组的属性，数组中值为 undefined 的元素是否进行移除，不对其进行拷贝，默认值为 false
 * @returns {Record<string, any>} 深拷贝后得到的对象
 */
export const copyDeep = (
  {
    origin,
    props = [],
    excludeProps = [],
    propsMap = {},
    targetPropValMap = {},
    excludeArrItem = () => false,
    sort = null,
    isCopyEmptyArr = true,
    isRemoveObjUndefined = false,
    isRemoveArrUndefined = false,
  }: {
    origin: Record<string, any>;
    props?: string[];
    excludeProps?: string[];
    propsMap?: Record<string, string | string[]>;
    targetPropValMap?: Record<string, (origin: Record<string, any>, originPropName: string, targetPropName: string) => any>;
    excludeArrItem?: (item: any) => boolean;
    sort?: ((a: any, b: any) => number) | null;
    isCopyEmptyArr?: boolean;
    isRemoveObjUndefined?: boolean;
    isRemoveArrUndefined?: boolean;
  }
) => {
  const target: Record<string, any> = {}
  // 遍历 origin 对象中所有的属性
  for (const key in origin) {
    // 如果当前遍历的属性名在 excludeProps 中，则跳过当前属性的拷贝
    if (excludeProps.length > 0 && excludeProps.includes(key)) continue
    // 如果指定了需要拷贝的属性，且当前遍历的属性不在属性名数组中，则跳过当前属性的拷贝
    if (props.length > 0 && !props.includes(key)) continue
    // 当前要拷贝属性的属性值
    let val = null
    // 如果当前遍历的属性值为 undefined
    if (origin[key] === undefined) {
      // 如果配置了不对属性值为 undefined 的属性进行拷贝，则跳过当前属性的拷贝
      if (isRemoveObjUndefined) continue
      val = origin[key]
    }
    // 如果当前遍历的属性的类型为 null, function, 则直接赋值
    else if (origin[key] === null || typeof origin[key] === 'function') {
      val = origin[key]
    }
    // 如果当前遍历的属性的类型为数组, 则使用 arrCopyDeep 函数进行深拷贝
    else if (Array.isArray(origin[key])) {
      val = arrCopyDeep({
        origin: origin[key],
        props,
        excludeProps,
        propsMap,
        targetPropValMap,
        excludeArrItem,
        sort,
        isCopyEmptyArr,
        isRemoveObjUndefined,
        isRemoveArrUndefined
      })
      // 如果当前遍历的属性的属性值为空数组，且配置了不对属性值为空数组的属性进行拷贝
      if (val.length === 0 && !isCopyEmptyArr) continue
    }
    // 如果如果当前遍历的属性的类型为对象，则递归调用自身进行深拷贝
    else if (typeof origin[key] === 'object') {
      val = copyDeep({
        origin: origin[key],
        props,
        excludeProps,
        propsMap,
        targetPropValMap,
        excludeArrItem,
        sort,
        isCopyEmptyArr,
        isRemoveObjUndefined,
        isRemoveArrUndefined
      })
    }
    // 其他情况直接赋值
    else {
      val = origin[key]
    }
    // 如果指定了当前遍历属性与目标对象中属性的映射关系，则使用映射关系进行赋值
    if (Reflect.has(propsMap, key)) {
      // 获取当前遍历属性需要映射到的目标对象属性名
      const mapTo = propsMap[key]
      // 如果需要映射到目标对象中的多个属性
      if (Array.isArray(mapTo)) {
        mapTo.forEach((propName) => {
          target[propName] = copyPropValHandler(origin, key, val, propName, targetPropValMap)
        })
      } else {
        target[mapTo] = copyPropValHandler(origin, key, val, mapTo, targetPropValMap)
      }
    } else {
      target[key] = copyPropValHandler(origin, key, val, key, targetPropValMap)
    }
  }
  return target
}
